#pragma once

#include "echo.hpp"
#include "limbo/ip/ipv4_address.h"
#include "state.hpp"

namespace limbo::icmp {

template <typename IcmpState> struct EchoResponse {
  using ContextState = EchoResponse;
  using Context = EchoResponse;
  using LowerState = IcmpState;
  using LowerContext = typename IcmpState::Context;
  using LowerPacket = typename LowerState::Packet;
  using IpState = typename IcmpState::LowerState;
  using IpContext = typename IpState::Context;
  using LinkContext = typename IpContext::LowerContext;
  using Packet = echo::Packet<EchoResponse, false>;

  static constexpr uint8_t SZ = 4;

  inline void init(const ip::IPv4Address &source, uint8_t ttl = 64) {
    if constexpr (details::has_lower_state_v<IpState>) {
      ip_ctx.link_context = &link_ctx_wrapper.ctx;
    }
    ip_ctx.source = source;
    ip_ctx.proto = ip::Proto::icmp;
    ip_ctx.ttl = ttl;
  }

  Result recv(Chunk buff, LowerState *lower_state) {
    return packet.recv(buff, *this, lower_state);
  }

  Packet &get_parsed() { return packet; }

  inline bool select(const LowerPacket *packet) {
    return (packet->type == Type::echo_response) &&
           (packet->container->destination == ip_ctx.source);
  }

  template <typename Stack, typename RequestPacket>
  Result send(Stack &stack, Chunk buff, const RequestPacket &request) {
    auto icmp = request.container;
    ip_ctx.destination = icmp->container->source;
    identifier = request.identifier;
    sequence_no = request.sequence_no;
    return stack.send(buff, *this, request.payload);
  }

  details::LinkContextWrapper<LinkContext> link_ctx_wrapper;
  IpContext ip_ctx;
  LowerContext icmp_ctx = {Type::echo_response, 0, &ip_ctx};
  Packet packet;
  uint16_t identifier;
  uint16_t sequence_no = 0;
  LowerState *state;
};

template <typename State>
inline auto *get_lower_context(EchoResponse<State> &ctx) {
  return &ctx.icmp_ctx;
}

template <typename State>
inline auto &get_link_context(EchoResponse<State> &ctx) {
  using IpState = typename State::LowerState;
  static_assert(
      details::has_lower_state_v<IpState>,
      "the function has meaning only when there is link state in the stack");
  auto icmp_ctx = get_lower_context(ctx);
  auto ip_ctx = get_lower_context(*icmp_ctx);
  auto link_ctx = get_lower_context(*ip_ctx);
  return *link_ctx;
}

} // namespace limbo::icmp
